{--
        Esqueleto de programa para geração de bubble cloud em Haskell.
        Mais informações em: http://www.inf.ufsm.br/~andrea/elc117-2012b

Aluno: Gabriel Machado Lunardi
Matricula: 2921723
Data: 26/11/2012

--}


module Main where

import Text.Printf -- Oba, Haskell tem printf! :-)
import Data.List


type Point     = (Float,Float)
type Color     = (Int,Int,Int)
type Circle    = (Point,Float) 

imageWidth :: Int
imageWidth = 360

imageHeight :: Int
imageHeight = 360


-- Funcao principal que faz leitura do dataset e gera arquivo SVG
main :: IO ()
main = do 
        strcontent <- readFile infile
        let pairs = map (span (/= ' ')) (lines strcontent)
            freqs = reverse(sort(readInts (map snd pairs))) -- fiz pra garantir que o dataset sempre esteja ordenado em ordem decrescente
        putStrLn "Gerando BubbleCloud, isso pode demorar alguns segundos ..."
        writeFile outfile (svgCloudGen imageWidth imageHeight freqs) -- assim a lista de circulos jah vai ser criada ordenada decrescente pelo raio
        putStrLn "Ok! SVG gerado com sucesso!"
        where 
                infile = "dataset.txt"
                outfile = "tagcloud.svg"


-- Transforma lista de strings em lista de inteiros
readInts :: [String] -> [Int]
readInts ss = map read ss



-- Configura o viewBox da imagem e coloca retangulo branco no fundo
svgViewBox :: Int -> Int -> String
svgViewBox w h =
        printf  "<svg width=\"%d\" height=\"%d\" viewBox=\"0 0 %d %d\"" w h w h ++ 
                " version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n" ++
        printf "<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"  stroke=\"black\" style=\"fill:white;\"/>\n" w h

-- Gera o documento SVG da tag cloud, concatenando cabecalho, conteudo e rodape
svgCloudGen :: Int -> Int -> [Int] -> String
svgCloudGen w h dataset = 
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" ++ 
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" ++
        (svgViewBox w h) ++
        (concat (svgBubbleGen w h dataset)) ++ "</svg>\n"


--                 ################################### motor ###################################

-- Esta funcao deve gerar a lista de circulos em formato SVG.
-- A implementacao atual eh apenas um teste que gera um circulo posicionado no meio da figura.
-- TODO: Alterar essa funcao para usar os dados do dataset.
svgBubbleGen:: Int -> Int -> [Int] -> [String]
svgBubbleGen w h dataset =  
        map svgCircle ( geraListaCirculos [ ((fromIntegral w/2, fromIntegral h/2), (head (geraListaRaios w h dataset))) ]  (tail(geraListaRaios w h dataset)) ((fromIntegral w/2, fromIntegral h/2),(head (geraListaRaios w h dataset))) a t )
                where a = 1
                      t = 0


-- Gera string representando um circulo em SVG.
svgCircle :: Circle -> String
svgCircle ((x,y),r) = 
        printf "<circle cx=\"%f\" cy=\"%f\" r=\"%f\" fill=\"rgb(%d,%d,%d)\" />\n" x y r (red r) (green r) (blue r)


--                                              ### RAIOS ###
-- cria a lista de raios usando map
geraListaRaios :: Int -> Int -> [Int] -> [Float]
geraListaRaios w h dataset = map (calculaRaio w) dataset

-- calcula um raio com base na percentagem de WIDTH
calculaRaio :: Int -> Int -> Float
calculaRaio w freq = ((fromIntegral freq)*(fromIntegral w*10/100))/1000 + 2 -- esse 5 eh pra deixar visivel o menor circulo


--                                          ### CIRCULOS E ESPIRAL ###
-- gera a lista de circulos validos, ou seja, que nao se sobrepoem
geraListaCirculos :: [Circle] -> [Float] -> Circle -> Float -> Float -> [Circle]
geraListaCirculos listCirc [] circMeio a t = listCirc -- qdo acabar os raios, acabou os circulos, termino recursao ;)
geraListaCirculos listCirc listRaios circMeio a t =  
        geraListaCirculos ( listCirc ++ (geraProxCircValido listCirc (head listRaios) circMeio a t) ) (tail listRaios) circMeio a t

-- gera circulos ateh achar um que nao sobreponha nenhum da lista de circulos valida
-- eh essa funcao que deve incrementar o t, pois enqto nao for valido, vai variando o angulo .. 
geraProxCircValido :: [Circle] -> Float -> Circle -> Float -> Float -> [Circle]
geraProxCircValido listCirc raio circMeio a t
        | verificaColisao (gera1Circulo t a raio circMeio) listCirc == True = [(gera1Circulo t a raio circMeio)] -- essa lista tem um circulo valido, que eh concatenada na funcao acima (acaba recursao)
        | otherwise = geraProxCircValido listCirc raio circMeio a (t+0.03) -- se o t eh mto pequeno, demora muito pra gerar, pois o passo eh mto pequeno
                -- continua a recursao caso o circulo gerado sobreponha algum circulo da lista de circulos valida
                
                
                
-- gera um circulo, valido ou nao.         
gera1Circulo :: Float -> Float -> Float -> Circle -> Circle
gera1Circulo t a raio circMeio = ((geraX t a (fst(fst(circMeio))) , geraY t a (snd(fst(circMeio)))),raio)

geraX :: Float -> Float -> Float -> Float
geraX t a x = x+(a*t*(cos t))

geraY :: Float -> Float -> Float -> Float
geraY t a y = y+(a*t*(sin t))


-- compara o circulo gerado com os validos da lista de circulos. 
verificaColisao :: Circle -> [Circle] -> Bool
verificaColisao circ listCirc = and (map (naoColide circ) listCirc) -- tendo um false nessa lista, retorna false indicando Circulo invalido

-- verifica se dois circulos nao colidem 
naoColide :: Circle -> Circle -> Bool
naoColide circ1 circ2 
        | ((dist2Pts (fst circ1) (fst circ2)) - ((snd circ1) + (snd circ2))) >= 0  = True -- não sobrepoe
        | otherwise = False -- sobrepoe
        
        
        
-- distancia entre dois pontos
dist2Pts :: Point -> Point -> Float
dist2Pts (x1,y1) (x2,y2) = sqrt(((x2 - x1)** 2) + ((y2 - y1)** 2))


--                                                      ## CORES ##
-- cores, sao usadas no momento da impressao dos circulos, svgCircle
red :: Float -> Int
red raio = (floor raio) * 7

green :: Float -> Int
green raio = (floor raio) * 3 

blue :: Float -> Int
blue raio = (floor raio) * 2

